﻿/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
* modify by Johnson - CoShare
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Qizx.Api;
using System.Qizx.XQuery;
using System.Xml;
using System.Xml.Schema;
using Comparison = System.Qizx.Util.Basic.Comparison;
using ComparisonContext = System.Qizx.XQuery.ComparisonContext;
using DataModelException = System.Qizx.Api.DataModelException;
using DateTimeException = System.Qizx.Api.Util.XQDateTimeException;
using Duration = System.Qizx.Api.Util.XQDuration;
using EvaluationException = System.Qizx.Api.EvaluationException;
using FileUtil = System.Qizx.Util.Basic.FileUtil;
using NamespaceContext = System.Qizx.Util.NamespaceContext;
using Node = System.Qizx.Api.Node;
using PushStreamToDOM = System.Qizx.Api.Util.PushStreamToDOM;
using QizxException = System.Qizx.Api.QizxException;
using QName = System.Qizx.Api.QName;
using StringValue = System.Qizx.XQuery.Dt.StringValue;
using UntypedAtomicType = System.Qizx.XQuery.Dt.UntypedAtomicType;
using URI = System.Uri;
using XMLPullStream = System.Qizx.Api.XMLPullStream;
using XQDateTimeBase = System.Qizx.Api.Util.XQDateTimeBase;
using XQItem = System.Qizx.XQuery.XQItem;
using XQValue = System.Qizx.XQuery.XQValue;

namespace System.Qizx.Xdm {


    [Serializable]
    public class XdmDocument : XdmElement {
        private XmlNameTable _nameTable;
        private XdmNameTable _xdmNameTable;
        private string _activeFileName;
        private bool reportValidity;
        internal bool CanReportValidity {
            get {
                return this.reportValidity;
            }
        }

        public override string BaseURI {
            get {
                return _activeFileName;
            }
        }

        public virtual void Load(string fileName) {
            _activeFileName = fileName;
            using (XdmReader reader = new XdmReader(fileName)) {
                reader.BuildDocument(this);
            }
            this._isValidated = true;
        }

        private int _schemaIndex;
        protected internal int ActiveSchemaSetIndex {
            get {
                return _schemaIndex;
            }
            set {
                _schemaIndex = value;
            }
        }

        /// <summary>
        /// Creates the schema set.
        /// Subclass can return XmlSchemaSet's subclass
        /// </summary>
        /// <returns></returns>
        protected virtual XmlSchemaSet CreateSchemas() {
            return new XmlSchemaSet(this.NameTable);
        }

        public override ItemSequence SelectItems(string xpath) {
            if (xpath.StartsWith("id(") && xpath.EndsWith(")")) {
                string elementId = xpath.Substring(3, xpath.Length - 4).Trim();
                ResultSequence sequence = new ResultSequence();
                if (this._idTable != null) {
                    List<WeakReference> list = null;
                    if (this._idTable.TryGetValue(elementId, out list)) {
                        foreach (WeakReference reference in list) {
                            XdmAttribute target = reference.Target as XdmAttribute;
                            if ((target != null) && target.IsRooted) {
                                sequence.addItem(target.OwnerElement);
                            }
                        }
                    }
                }
                return sequence;
            }

            return base.SelectItems(xpath);
        }

        public override ItemSequence SelectItems(string xpath, IXmlNamespaceResolver resolver) {
            if (xpath.StartsWith("id(") && xpath.EndsWith(")")) {
                return this.SelectItems(xpath);
            }

            return base.SelectItems(xpath, resolver);
        }

        /// <summary>
        /// The XdmElement with the matching ID or null reference if no matching element is found.
        /// </summary>
        /// <remarks>If the document has multiple elements with the matching ID, this method returns the first matching element in the document.</remarks>
        /// <param name="elementId">The element id.</param>
        /// <returns></returns>
        public virtual XdmElement GetElementById(string elementId) {

            if (this._idTable != null) {
                List<WeakReference> list = null;
                if (_idTable.TryGetValue(elementId, out list)) {
                    foreach (WeakReference reference in list) {
                        XdmAttribute target = reference.Target as XdmAttribute;
                        if ((target != null) && target.IsRooted) {
                            return target.OwnerElement;
                        }
                    }
                }
            }
            return null;

            /*
            XQuerySessionManager manager = new XQuerySessionManager(new Uri(this.BaseURI));
            XQuerySession session = manager.CreateSession();
            Expression expression = session.CompileExpression(".//*[@id='" + elementId + "']");
            expression.CurrentItem = this;
            ItemSequence sequence = expression.Evaluate();
            if (sequence.MoveToNextItem())
                return sequence.IsNode ? sequence.GetNode() as XdmElement : null;
            return null;
             * */
        }

        public XmlNameTable NameTable {
            get {
                if (_nameTable == null)
                    _nameTable = new NameTable();
                return _nameTable;
            }
        }

        public XdmDocument(CoreDataModel model)
            : base(model, null) {
            _xdmNameTable = new XdmNameTable(this);
            _idTable = new Dictionary<string, List<WeakReference>>();
        }

        public XdmDocument(XmlNameTable nameTable, CoreDataModel model)
            : base(model, null) {
            _nameTable = nameTable;
            _xdmNameTable = new XdmNameTable(this);
            _idTable = new Dictionary<string, List<WeakReference>>();
        }

        public XdmDocument()
            : this(new CoreDataModel(string.Empty)) {
        }

        public XdmElement CreateElement(string name) {
            string prefix = string.Empty;
            string localName = string.Empty;
            XdmNodeExtensions.SplitName(name, out prefix, out localName);
            return this.CreateElement(prefix, localName, string.Empty);
        }
        public XdmElement CreateElement(string qualifiedName, string namespaceURI) {
            string prefix = string.Empty;
            string localName = string.Empty;
            XdmNodeExtensions.SplitName(qualifiedName, out prefix, out localName);
            return this.CreateElement(prefix, localName, namespaceURI);

        }
        public virtual XdmElement CreateElement(string prefix, string localName, string namespaceURI) {
            XdmElement elem = new XdmElement(this.AddXmlName(prefix, localName, namespaceURI, null), true, this);
            return elem;
        }

        internal XdmName AddXmlName(string qualifiedName, string namespaceURI, IXdmSchemaInfo schemaInfo) {
            string prefix = string.Empty;
            string localName = string.Empty;
            XdmNodeExtensions.SplitName(qualifiedName, out prefix, out localName);
            return this._xdmNameTable.AddName(prefix, localName, namespaceURI, schemaInfo);
        }

        internal XdmName AddXmlName(string prefix, string localName, string namespaceURI, IXdmSchemaInfo schemaInfo) {
            return this._xdmNameTable.AddName(prefix, localName, namespaceURI, schemaInfo);
        }

        public XdmText CreateTextNode(string text) {
            return new XdmText(text, this);
        }

        public XdmCDataSection CreateCDataSection(string text) {
            return new XdmCDataSection(text, this);
        }

        public XdmComment CreateComment(string text) {
            return new XdmComment(text, this);
        }
        public XdmAttribute CreateAttribute(string prefix, string localName, string namespaceURI) {
            if (prefix == "xmlns") {
                return new XdmNSAttribute(this.AddXmlName(prefix, localName, namespaceURI, null), this);
            } else if (localName == "xmlns" && string.IsNullOrEmpty(prefix)) {
                return new XdmNSAttribute(this.AddXmlName(localName, string.Empty, namespaceURI, null), this);
            } else {
                return new XdmAttribute(this.AddXmlName(prefix, localName, namespaceURI, null), this);
            }

        }

        public XdmNSAttribute CreateNSAttribute(string prefix, string namespaceURI) {
            XdmNSAttribute ns = new XdmNSAttribute(this.AddXmlName("xmlns", prefix, "http://www.w3.org/2000/xmlns/", null), this);
            ns.AddText(namespaceURI);
            return ns;
        }
        public XdmProcessingInstruction CreateProcessingInstruction(string target, string data) {
            return new XdmProcessingInstruction(target, data, this);
        }
        public override string NodeKind {
            get {
                return "document";
            }

        }
        public override XdmNodeType NodeType {
            get {
                return System.Qizx.Api.XdmNodeType.Document;
            }

        }

        public override bool AddAttribute(XdmAttribute attr) {
            throw new DataModelException("XPTY0004", "adding attribute to a document node");
        }

        #region Extensions
        public XdmElement DocumentElement {
            get {
                return (XdmElement)this.FindChild(XdmNodeType.Element);
            }
        }

        public virtual void Save(string fileName) {
            if (this.DocumentElement == null) return;

            using (XdmTextWriter writer = new XdmTextWriter(fileName, Text.Encoding.UTF8)) {
                writer.Formatting = Formatting.Indented;
                writer.Indentation = 2;
                writer.IndentChar = ' ';
                this.WriteTo(writer);
                writer.Flush();
                writer.Close();
            }
        }
        public override void WriteContentTo(XmlWriter w) {
            foreach (XdmNode node in this) {
                node.WriteTo(w);
            }
        }
        public override void WriteTo(XmlWriter w) {
            w.WriteStartDocument();
            WriteContentTo(w);
            w.WriteEndDocument();
        }

        public override IXdmSchemaInfo SchemaInfo {
            get {
                if (this._isValidated) {
                    XdmElement documentElement = this.DocumentElement;
                    if (documentElement != null) {
                        switch (documentElement.SchemaInfo.Validity) {
                            case XmlSchemaValidity.Valid:
                                return BasicNode._ValidSchemaInfo;

                            case XmlSchemaValidity.Invalid:
                                return BasicNode._InvalidSchemaInfo;
                        }
                    }
                }
                return BasicNode._NotKnownSchemaInfo;
            }
        }

        private bool _isValidated;
        private XmlSchemaSet _schemas;
        /// <summary>
        /// Gets or sets the XmlSchemaSet object associated with this XmlDocument.
        /// <return>An XmlSchemaSet object containing the XML Schema Definition Language (XSD) schemas associated with this XmlDocument; otherwise, an empty XmlSchemaSet object.</return>
        /// </summary>
        /// <remarks></remarks>
        public virtual XmlSchemaSet Schemas {
            get {
                if (_schemas == null)
                    _schemas = new XmlSchemaSet(this.NameTable);
                return _schemas;
            }
            set { _schemas = value; }
        }

        #endregion

        #region cache the id elements
        protected Dictionary<string, List<WeakReference>> _idTable;
        protected internal void AddIdAttribute(XdmAttribute attribute) {
            string id = attribute.StringValue;
            List<WeakReference> attrs = null;
            if (!_idTable.TryGetValue(id, out attrs)) {
                attrs = new List<WeakReference>();
                _idTable[id] = attrs;
            }
            attrs.Add(new WeakReference(attribute));
        }

        protected internal void RemoveIdAttribute(XdmAttribute attribute) {
            string id = attribute.StringValue;
            List<WeakReference> attrs = null;
            if (_idTable.TryGetValue(id, out attrs)) {
                WeakReference cacheRef = GetXdmAttribute(attrs, attribute);
                if (cacheRef != null) {
                    attrs.Remove(cacheRef);
                    if (attrs.Count == 0)
                        _idTable.Remove(id);
                }
            }
        }

        private WeakReference GetXdmAttribute(List<WeakReference> attributeRefs, XdmAttribute elem) {
            for (int i = attributeRefs.Count - 1; i > -1; i--) {
                WeakReference reference = attributeRefs[i];
                if (!reference.IsAlive) {
                    attributeRefs.RemoveAt(i);
                } else if (((XdmAttribute)reference.Target) == elem) {
                    return reference;
                }
            }
            return null;
        }

        public virtual XdmNode ImportNode(Node node, bool deep) {
            NamespaceContext nsContext = new NamespaceContext();
            switch (node.NodeType) {
                case XdmNodeType.Document:
                    if (deep) { ReadChildren(node, this, nsContext); }
                    return this;
                case XdmNodeType.Element:

                    XdmElement newElement = this.CreateElement(node.Prefix, node.LocalName, node.NamespaceURI);
                    int count = (node as BasicNode).AddNamespacesTo(nsContext);
                    if (count > 0) {
                        for (int index = 0; index < count; index++) {
                            newElement.AddNamespace(this.CreateNSAttribute(nsContext.getLocalPrefix(index), nsContext.getLocalNamespaceURI(index)));
                        }
                    }
                    nsContext.clear();

                    #region Read attriutes
                    if (node.AttributeCount > 0) {
                        foreach (Node attr in node.Attributes) {
                            XdmAttribute attribute = CreateAttribute(attr.Prefix, attr.LocalName, attr.NamespaceURI);
                            attribute.TextValue = attr.StringValue;
                            newElement.AddAttribute(attribute);
                        }
                    }

                    #endregion
                    if (deep)
                        ReadChildren(node, newElement, nsContext);
                    return newElement;
                case XdmNodeType.Text:
                    XdmNode textNode = CreateTextNode(node.StringValue);
                    return textNode;
                case XdmNodeType.Comment:
                    XdmNode commentNode = CreateComment(node.StringValue);
                    return commentNode;
                case XdmNodeType.Attribute:
                    return CreateAttribute(node.Prefix, node.LocalName, node.NamespaceURI);
                default:
                    return null;
            }
        }

        private void ReadChildren(Node reader, XdmNode currParentNode, NamespaceContext nsContext) {
            for (Node childNode = reader.FirstChild; childNode != null; childNode = childNode.NextSibling) {
                switch (childNode.NodeType) {
                    case XdmNodeType.Document:
                    case XdmNodeType.Element:

                        XdmElement newElement = this.CreateElement(childNode.Prefix, childNode.LocalName, childNode.NamespaceURI);
                        int count = (childNode as BasicNode).AddNamespacesTo(nsContext);
                        if (count > 0) {
                            for (int index = 0; index < count; index++) {
                                newElement.AddNamespace(this.CreateNSAttribute(nsContext.getLocalPrefix(index), nsContext.getLocalNamespaceURI(index)));
                            }
                        }
                        nsContext.clear();
                        #region Read attriutes
                        if (childNode.AttributeCount > 0) {
                            foreach (Node attr in childNode.Attributes) {
                                XdmAttribute attribute = CreateAttribute(attr.Prefix, attr.LocalName, attr.NamespaceURI);
                                attribute.TextValue = attr.StringValue;
                                newElement.AddAttribute(attribute);
                            }
                        }

                        #endregion
                        currParentNode.AppendChild(newElement);
                        if (childNode.SchemaInfo != null && childNode.SchemaInfo.DataType != null && childNode.SchemaInfo.DataType.TypeCode == XmlTypeCode.QName) {
                            try {
                                QName value = childNode.ValueAsQName;
                                string prefix = childNode.GetPrefixOfNamespace(value.Namespace);
                                string newPrefix = newElement.GetPrefixOfNamespace(value.Namespace);
                                if (newPrefix == null) {
                                    XdmNSAttribute ns = this.CreateNSAttribute(prefix, value.Namespace);
                                    newElement.AddNamespace(ns);
                                    newPrefix = prefix;
                                }
                                string fullName = string.IsNullOrEmpty(newPrefix) ? value.Name : prefix + ":" + value.Name;
                                XdmNode qnameNode = CreateTextNode(fullName);
                                newElement.AppendChild(qnameNode);
                            } catch {
                                ReadChildren(childNode, newElement, nsContext);
                            }
                        } else {
                            ReadChildren(childNode, newElement, nsContext);
                        }
                        break;
                    case XdmNodeType.Text:
                        XdmNode textNode = CreateTextNode(childNode.StringValue);
                        currParentNode.AppendChild(textNode);
                        break;
                    case XdmNodeType.Comment:
                        XdmNode commentNode = CreateComment(childNode.StringValue);
                        currParentNode.AppendChild(commentNode);
                        break;
                    case XdmNodeType.ProcessingInstruction:
                        XdmProcessingInstruction instr = childNode as XdmProcessingInstruction;
                        if (instr != null) {
                            XdmNode procIns = CreateProcessingInstruction(instr.target, instr.StringValue);
                            currParentNode.AppendChild(procIns);
                        }
                        break;
                }
            }
        }

        protected internal void UpdateIdAttribute(XdmAttribute attribute, string oldValue, string newValue) {
            if (oldValue == newValue) return;

            string id = oldValue;
            List<WeakReference> attrs = null;
            if (_idTable.TryGetValue(id, out attrs)) {
                WeakReference cacheRef = GetXdmAttribute(attrs, attribute);
                if (cacheRef != null) {
                    attrs.Remove(cacheRef);
                    if (attrs.Count == 0)
                        _idTable.Remove(id);
                }
            }

            id = newValue;
            if (!_idTable.TryGetValue(id, out attrs)) {
                attrs = new List<WeakReference>();
                _idTable[id] = attrs;
            }
            attrs.Add(new WeakReference(attribute));
        }
        #endregion
    }
}
